#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<ctype.h>//isspace头文件
#include<fcntl.h>//文件操作open


#define LEFT "["
#define RIGHT "]"
#define LABLE "#"
#define LINE_SIZE 1024
#define ARGC_SIZE 32
#define DELIM " "//分隔符
#define EXIT_CODE 444


extern char **environ;
char commandline[LINE_SIZE];
char *argv[ARGC_SIZE];//获得命令行参数

int lastcode = 0;//子进程退出码
int quit = 0;//命令行持续执行
char pwd[LINE_SIZE];//获取路径cwd

char myenv[LINE_SIZE];//放环境变量

//-----加上文件操作
#define NONE   -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2
char *rdirfilename = NULL;
int rdir = NONE;


const char *getusername()
{
   return getenv("USER");
}

const char *Gethostname()
{
    return getenv("HOSTNAME");
    
}

//const char *getpwd()
void getpwd()
{
 //   return getenv("PWD");
    getcwd(pwd,sizeof(pwd)-1);
}

//---------文件操作
void checkrdir(char *cmd)
{
    //ls -a -l
    //ls -a -l > file.txt
    char *pos = cmd;
    while(*pos)
    {
        if(*pos == '>')//输出重定向
        {
            if(*(pos+1) == '>')
            {
                //追加
                *pos = '\0';

                pos++;
                *pos = '\0';
                pos++;
                //一个指令一分为二
                //
                while(isspace(*pos)) pos++;//检查是否空格,是空格一直++
                rdirfilename = pos; 
                rdir = APPEND_RDIR;//追加重定向
                break;
            }
            else
            {
                *pos = '\0';
                pos++;
                while(isspace(*pos)) pos++;//检查是否空格,是空格一直++
                rdirfilename = pos; 
                rdir = OUT_RDIR;//输出重定向
                break;
            }
        }
        else if(*pos == '<')//输入重定向
        {
            *pos = '\0';
            pos++;
            while(isspace(*pos)) pos++;//检查是否空格,是空格一直++
    
            rdirfilename = pos; 

            rdir = IN_RDIR;//输入重定向
            break;
        }
        else
        {}
        pos++;
    }
}


void interact(char* cline,int size)
{
    getpwd();
    printf(LEFT"%s@%s %s"RIGHT""LABLE"",getusername(),Gethostname(),pwd);
    char *s = fgets(cline,size,stdin);
    assert(s);
    (void*)s;//避免日后只定义不使用引起warning
    cline[strlen(cline)-1] = '\0';

    //-------文件操作
    //ls -a -l > myfile.txt
    //从右往左扫描/遍历，看是否有 > < 等符号
    checkrdir(cline);
    
}

//分割命令行参数
int splitstring(char* cline,char* _argv[])
{
    int i =0;// strtok();分割函数
    _argv[i++] = strtok(cline,DELIM);
    while(_argv[i++] = strtok(NULL,DELIM));//注意:第二次传参数传NULL，默认返回前一次之后

    return i-1;//返回参数数量下标
}

void NormalExcute(char *_argv[])
{
    //5.普通命令执行
     pid_t id = fork();
     if(id < 0){
         perror("fork");
         return;
     }
     else if(id == 0){
        int fd = 0;
        if(rdir == IN_RDIR)
        {
            fd = open(rdirfilename,O_RDONLY);
            dup2(fd,0);
        }
        else if(rdir == OUT_RDIR)
        {
            fd = open(rdirfilename,O_CREAT|O_WRONLY|O_TRUNC,0666);
            dup2(fd,1);

        }
        else if(rdir == APPEND_RDIR)
        {
            fd = open(rdirfilename,O_CREAT|O_WRONLY|O_APPEND,0666);
            dup2(fd,1);
        }

         //子-让子进程执行命令
         //execvpe(_argv[0],_argv,environ);
         execvp(_argv[0],_argv);//也能拿到环境变量
         exit(EXIT_CODE);
     }
     else
     {
         int status = 0;
         pid_t rid = waitpid(id,&status,0);
         if(rid == id)
         {
             lastcode = WEXITSTATUS(status);//保留退出码
         }
     }
}

int builtCommand(char *_argv[],int _argc)
{
        if(_argc == 2 && strcmp(_argv[0],"cd") == 0)
        {
            chdir(_argv[1]);
            //更新环境变量pwd
            getpwd();
//            sprintf(getenv("PWD"),"%s",argv[1]);//直接去改环境变量
            sprintf(getenv("PWD"),"%s",pwd);//不用从环境变量获取方式
            return 1;
        }
        else if(_argc == 2 && strcmp(_argv[0],"export") == 0)
        {
            //环境变量表里存的是地址，导入后保留的是_argv地址，但是，不能这样:_argv是共享的,需要拷贝保留
            strcpy(myenv,_argv[1]);//可以直接malloc,也可以二维数组，这就弄了一个
            putenv(myenv);
            return 1;
        }
        else if(_argc == 2 && strcmp(_argv[0],"echo") == 0)
        {
           if(strcmp(_argv[1] , "$?") ==0 )
           {
               printf("%d\n",lastcode);
               lastcode = 0;
           }
           else if(*_argv[1] == '$') //argv[1]的第一个字符是"$",证明这是变量
           {
               char *val = getenv(_argv[1]+1);//找到$之后的字符地址，得到变量
               if(val)
                   printf("%s\n",val);
               else
                   printf("NULL\n");
           }
           else
           {
                printf("%s\n",argv[1]);
           }
           return 1;
        }

        //特殊处理 ls颜色问题
        if(strcmp(argv[0],"ls") == 0)
        {
            _argv[_argc++] = "--color";
            _argv[_argc] = NULL;
        }
        return 0;//不是内建命令返回0
}

int main()
{

    while(!quit)
    {
        //1.清空流
        rdirfilename = NULL;
        rdir = NONE;
        

        //2.交互问题，获取命令
        interact(commandline,sizeof(commandline));
        //printf("echo: %s\n",commandline);
        
        //3.字符串分割问题
       int argc = splitstring(commandline,argv);
       if(argc == 0) continue;

        //debug
//        for(int i =0;argv[i];i++)   printf("[%d]:%s\n",i,argv[i]);


        //4.指令判断
        //哪些是普通命令，哪些是内建命令(必须父进程自己执行)        

        //内建命令-列举
        //本质就是一个shell内部的一个函数

        int n = builtCommand(argv,argc);

        //普通命令
        if(n == 0 )
            NormalExcute(argv);

    }
    return 0;
}
